Transparence référentielle
La transparence référentielle est une propriété des expressions d'un langage de programmation qui fait qu'une expression peut être remplacée par sa valeur sans changer le comportement du programme.
Définition
[modifier | modifier le code]- Une expression est référentiellement transparente si elle peut être remplacée par sa valeur sans changer le comportement du programme (c'est-à-dire que le programme a les mêmes effets et les mêmes sorties pour les mêmes entrées, quel que soit son contexte d'exécution).
- Une expression est référentiellement opaque si elle n'est pas référentiellement transparente.
Si toutes les fonctions impliquées dans l'expression sont pures, c'est-à-dire si elles ont toujours les mêmes valeurs de retour pour les mêmes arguments et si elles sont sans effets de bord, alors l'expression est référentiellement transparente. Mais la réciproque est fausse : une expression référentiellement transparente peut impliquer des fonctions impures.
La transparence référentielle est la pierre angulaire de la programmation fonctionnelle. Elle permet notamment la mémoïsation des fonctions référentiellement transparentes (c'est-à-dire la mémorisation des valeurs précédemment calculées).
Exemples et contre-exemples
[modifier | modifier le code]Les opérations arithmétiques sont référentiellement transparentes :
on peut ainsi remplacer 4*3
par sa valeur 12
ou par 2*6
ou par 3*4
.
Les fonctions au sens mathématique du terme sont référentiellement transparentes : c'est le cas par exemple de la fonction sin(x)
qui est référentiellement transparente, puisqu'elle renvoie toujours le même résultat pour un x
donné et n'a pas d'effets de bord.
En revanche, l'expression x++
du langage C n'est pas référentiellement transparente, car elle change la valeur de x
. Il est de même de today()
qui n'est pas référentiellement transparent, car si on l'évalue aujourd'hui on n'obtient pas le même résultat que si on l'exécute demain.
Contraste avec la programmation impérative
[modifier | modifier le code]Si la substitution d'une expression par sa valeur est valide seulement à certains points d'un programme, alors l'expression n'est pas référentiellement transparente. La définition et l'ordre de ses points de séquence (en) sont la fondation théorique de la programmation impérative, et une porte de la sémantique des langages de programmation impératifs.[pas clair]
Néanmoins, puisqu'une expression référentiellement transparente peut toujours être remplacée par sa valeur ou par une expression produisant la même valeur, il n'est pas nécessaire de définir des points de séquence ou de préserver l'ordre d'évaluation. On appelle programmation purement fonctionnelle un paradigme de programmation exempt des soucis d'un ordonnancement figé des exécutions.
L'avantage d'écrire du code référentiellement transparent est que l'analyse de code statique est plus facile et que des transformations automatiques d'amélioration de code sont possibles. Par exemple, en programmation en C, inclure une fonction dans une boucle se paye en matière de performances, alors que cette fonction pourrait être déplacée hors de la boucle sans changer les résultats du programme. En déplaçant cette fonction, le programmeur peut y perdre en lisibilité du code. Mais, si le compilateur est capable de déterminer la transparence référentielle de l'appel de la fonction, il effectuera automatiquement ce déplacement produisant ainsi du code plus efficace.
Les langages imposant la transparence référentielle rendent malaisée l'écriture d'opérations qui s'expriment naturellement comme une suite de pas. De tels langages peuvent incorporer des mécanismes pour rendre ces tâches plus faciles tout en retenant leur qualité purement fonctionnelle telles que les monades et les definite clause grammars.
Alors qu'en mathématiques toutes les applications de fonction sont référentiellement transparentes, en programmation impérative ça n'est pas toujours le cas. Par exemple dans le cas d'une fonction sans paramètre qui retourne une chaîne saisie au clavier, la valeur de retour dépend de la valeur saisie, donc de multiples appels de la fonction avec des paramètres identiques (la liste vide) peuvent retourner des résultats différents.
Un appel à une fonction qui utilise une variable globale, ou une variable avec une portée dynamique ou lexicale pour aider à calculer ses résultats, n'est pas référentiellement transparente. Puisque cette variable n'est pas passée comme paramètre mais peut être altérée, les résultats des appels suivants à la fonction peuvent différer même si les paramètres sont identiques. Dans les langages fonctionnels purs, l'affectation n'est pas autorisée, en revanche l'utilisation de monades permet une programmation fonctionnelle pure tout en ayant un mécanisme qui ressemble de près aux variables globales[1].
L'importance de la transparence référentielle est qu'elle permet au programmeur ou au compilateur de raisonner sur le comportement du programme. Cela peut aider à prouver qu'il est correct, à trouver des bogues qui ne peuvent être trouvées par des tests, à simplifier un algorithme, à aider à modifier du code sans le casser, à optimiser le code par le moyen de la mémorisation ou éliminer des sous-expressions communes.
Certains langages de programmation fonctionnelle (comme Haskell) imposent la transparence référentielle pour toutes les fonctions.
Principe de séparation commande-requête
[modifier | modifier le code]Eiffel, bien que basé sur la programmation impérative, impose une séparation stricte entre les commandes qui peuvent produire des effets de bord et les requêtes qui doivent être référentiellement transparentes. Les requêtes retournent un résultat mais ne changent pas l'environnement. On appelle cette règle le principe de séparation commande-requête qui permet d'aboutir à un style qui sépare clairement les parties référentiellement transparentes des autres. Par exemple dans la manipulation de listes :
my_list.finish -- Déplace le curseur à la fin de la liste value := my_list.item -- Obtient la valeur à la position du curseur : référentiellement transparent
Cela affecte même des fonctionnalités impératives comme les entrées :
my_file.read_integer -- obtient le prochain entier; effet de bord, mais pas de valeur de retour value := my_file.last_integer -- obtient le dernier entier lu: réferentiellement transparent
Appeler plusieurs fois en série last_integer
donne le même résultat à chaque fois.
Autre exemple
[modifier | modifier le code]Soit deux fonctions, dont l'une rq
est référentiellement opaque, et l'autre rt
référentiellement transparente :
globalValue = 0; integer function rq(integer x) begin globalValue = globalValue + 1; return x + globalValue; end
integer function rt(integer x) begin return x + 1; end
Dire que rt
est référentiellement transparent, signifie que pour un n donné rt
(n) produira toujours la même valeur.
Par exemple, rt(6) = 7, rt(4) = 5
, et ainsi de suite.
Mais ce n'est pas vrai de rq
car il utilise une variable globale qu'il modifie.
Voir aussi
[modifier | modifier le code]- Effet de bord
- Fonction déterministe
- Fonction pure
- Fonction récursive
- Idempotence
- Réentrance
- Thread safety
Références
[modifier | modifier le code]- En gros, quand on utilise une monade
State
en Haskell et qu'on l'applique àInt
pour obtenirStat Int
, c'est comme s'il y avait une variable globale de typeInt
, pas une de moins, pas une de plus. On a donc, à tout moment, grâce au type, une connaissance complète et statique des variables globales que l'on utilise.